using System;
using System.ComponentModel;
using System.Globalization;
using SharpDX;

namespace Engine
{
	[TypeConverter(typeof(ExpandableObjectConverter))]
	[Serializable]
	public class BoundingFrustum : IEquatable<BoundingFrustum>
	{
		public const int CornerCount = 8;
		private const int NearPlaneIndex = 0;
		private const int FarPlaneIndex = 1;
		private const int LeftPlaneIndex = 2;
		private const int RightPlaneIndex = 3;
		private const int TopPlaneIndex = 4;
		private const int BottomPlaneIndex = 5;
		private const int NumPlanes = 6;
		private Matrix matrix;
		private Plane[] planes = new Plane[6];
		internal Vector3[] cornerArray = new Vector3[8];
		private Gjk gjk;
		public Plane Near
		{
			get
			{
				return this.planes[0];
			}
		}
		public Plane Far
		{
			get
			{
				return this.planes[1];
			}
		}
		public Plane Left
		{
			get
			{
				return this.planes[2];
			}
		}
		public Plane Right
		{
			get
			{
				return this.planes[3];
			}
		}
		public Plane Top
		{
			get
			{
				return this.planes[4];
			}
		}
		public Plane Bottom
		{
			get
			{
				return this.planes[5];
			}
		}
		public Matrix Matrix
		{
			get
			{
				return this.matrix;
			}
			set
			{
				this.SetMatrix(ref value);
			}
		}
		public Vector3[] GetCorners()
		{
			return (Vector3[])this.cornerArray.Clone();
		}
		public void GetCorners(Vector3[] corners)
		{
			if (corners == null)
			{
				throw new ArgumentNullException("corners");
			}
			if (corners.Length < 8)
			{
				throw new ArgumentOutOfRangeException("corners");
			}
			this.cornerArray.CopyTo(corners, 0);
		}
		public bool Equals(BoundingFrustum other)
		{
			return !(other == null) && this.matrix == other.matrix;
		}
		public override bool Equals(object obj)
		{
			bool result = false;
			BoundingFrustum boundingFrustum = obj as BoundingFrustum;
			if (boundingFrustum != null)
			{
				result = (this.matrix == boundingFrustum.matrix);
			}
			return result;
		}
		public override int GetHashCode()
		{
			return this.matrix.GetHashCode();
		}
		public override string ToString()
		{
			CultureInfo currentCulture = CultureInfo.CurrentCulture;
			return string.Format(currentCulture, "{{Near:{0} Far:{1} Left:{2} Right:{3} Top:{4} Bottom:{5}}}", new object[]
			{
				this.Near.ToString(), 
				this.Far.ToString(), 
				this.Left.ToString(), 
				this.Right.ToString(), 
				this.Top.ToString(), 
				this.Bottom.ToString()
			});
		}
		private BoundingFrustum()
		{
		}
		public BoundingFrustum(Matrix value)
		{
			this.SetMatrix(ref value);
		}
		private void SetMatrix(ref Matrix value)
		{
			this.matrix = value;
			this.planes[2].Normal.X = -value.M14 - value.M11;
			this.planes[2].Normal.Y = -value.M24 - value.M21;
			this.planes[2].Normal.Z = -value.M34 - value.M31;
			this.planes[2].D = -value.M44 - value.M41;
			this.planes[3].Normal.X = -value.M14 + value.M11;
			this.planes[3].Normal.Y = -value.M24 + value.M21;
			this.planes[3].Normal.Z = -value.M34 + value.M31;
			this.planes[3].D = -value.M44 + value.M41;
			this.planes[4].Normal.X = -value.M14 + value.M12;
			this.planes[4].Normal.Y = -value.M24 + value.M22;
			this.planes[4].Normal.Z = -value.M34 + value.M32;
			this.planes[4].D = -value.M44 + value.M42;
			this.planes[5].Normal.X = -value.M14 - value.M12;
			this.planes[5].Normal.Y = -value.M24 - value.M22;
			this.planes[5].Normal.Z = -value.M34 - value.M32;
			this.planes[5].D = -value.M44 - value.M42;
			this.planes[0].Normal.X = -value.M13;
			this.planes[0].Normal.Y = -value.M23;
			this.planes[0].Normal.Z = -value.M33;
			this.planes[0].D = -value.M43;
			this.planes[1].Normal.X = -value.M14 + value.M13;
			this.planes[1].Normal.Y = -value.M24 + value.M23;
			this.planes[1].Normal.Z = -value.M34 + value.M33;
			this.planes[1].D = -value.M44 + value.M43;
			for (int i = 0; i < 6; i++)
			{
				float num = this.planes[i].Normal.Length();
				Plane[] expr_359_cp_0 = this.planes;
				int expr_359_cp_1 = i;
				expr_359_cp_0[expr_359_cp_1].Normal = expr_359_cp_0[expr_359_cp_1].Normal / num;
				Plane[] expr_376_cp_0 = this.planes;
				int expr_376_cp_1 = i;
				expr_376_cp_0[expr_376_cp_1].D = expr_376_cp_0[expr_376_cp_1].D / num;
			}
			Ray ray = BoundingFrustum.ComputeIntersectionLine(ref this.planes[0], ref this.planes[2]);
			this.cornerArray[0] = BoundingFrustum.ComputeIntersection(ref this.planes[4], ref ray);
			this.cornerArray[3] = BoundingFrustum.ComputeIntersection(ref this.planes[5], ref ray);
			ray = BoundingFrustum.ComputeIntersectionLine(ref this.planes[3], ref this.planes[0]);
			this.cornerArray[1] = BoundingFrustum.ComputeIntersection(ref this.planes[4], ref ray);
			this.cornerArray[2] = BoundingFrustum.ComputeIntersection(ref this.planes[5], ref ray);
			ray = BoundingFrustum.ComputeIntersectionLine(ref this.planes[2], ref this.planes[1]);
			this.cornerArray[4] = BoundingFrustum.ComputeIntersection(ref this.planes[4], ref ray);
			this.cornerArray[7] = BoundingFrustum.ComputeIntersection(ref this.planes[5], ref ray);
			ray = BoundingFrustum.ComputeIntersectionLine(ref this.planes[1], ref this.planes[3]);
			this.cornerArray[5] = BoundingFrustum.ComputeIntersection(ref this.planes[4], ref ray);
			this.cornerArray[6] = BoundingFrustum.ComputeIntersection(ref this.planes[5], ref ray);
		}

        public BoundingFrustum(Vector3 View, Vector3 Right, Vector3 Up)
        {
            this.planes[0].D = 0;
            this.planes[0].Normal = -Vector3.Normalize(View);

            this.planes[1].D = -View.Length();
            this.planes[1].Normal = Vector3.Normalize(View);

            this.planes[2].D = -Right.Length();
            this.planes[2].Normal = -Vector3.Normalize(Right);

            this.planes[3].D = -Right.Length();
            this.planes[3].Normal = Vector3.Normalize(Right);

            this.planes[4].D = -Up.Length();
            this.planes[4].Normal = Vector3.Normalize(Up);

            this.planes[5].D = -Up.Length();
            this.planes[5].Normal = -Vector3.Normalize(Up);


            this.cornerArray[0] = Up - Right;
            this.cornerArray[1] = Up + Right;
            this.cornerArray[2] = -Up + Right;
            this.cornerArray[3] = -Up - Right;

            this.cornerArray[4] = Up - Right + View;
            this.cornerArray[5] = Up + Right + View;
            this.cornerArray[6] = -Up + Right + View;
            this.cornerArray[7] = -Up - Right + View;

        }

		private static Ray ComputeIntersectionLine(ref Plane p1, ref Plane p2)
		{
			Ray result = default(Ray);
			result.Direction = Vector3.Cross(p1.Normal, p2.Normal);
			float divider = result.Direction.LengthSquared();
			result.Position = Vector3.Cross(-p1.D * p2.Normal + p2.D * p1.Normal, result.Direction) / divider;
			return result;
		}
		private static Vector3 ComputeIntersection(ref Plane plane, ref Ray ray)
		{
			float scaleFactor = (-plane.D - Vector3.Dot(plane.Normal, ray.Position)) / Vector3.Dot(plane.Normal, ray.Direction);
			return ray.Position + ray.Direction * scaleFactor;
		}
		public bool Intersects(BoundingBox box)
		{
			bool result;
			this.Intersects(ref box, out result);
			return result;
		}
		public void Intersects(ref BoundingBox box, out bool result)
		{
			if (this.gjk == null)
			{
				this.gjk = new Gjk();
			}
			this.gjk.Reset();
            Vector3 closestPoint = this.cornerArray[0] - box.Minimum;
			//Vector3.Subtract(ref this.cornerArray[0], ref box.Min, out closestPoint);
			if (closestPoint.LengthSquared() < 1E-05f)
			{
                closestPoint = this.cornerArray[0] - box.Maximum;
				//Vector3.Subtract(ref this.cornerArray[0], ref box.Max, out closestPoint);
			}
			float num = 3.40282347E+38f;
			result = false;
			while (true)
			{
				Vector3 vector;
				vector.X = -closestPoint.X;
				vector.Y = -closestPoint.Y;
				vector.Z = -closestPoint.Z;
				Vector3 vector2;
				this.SupportMapping(ref vector, out vector2);
				Vector3 vector3;

                //box.SupportMapping(ref closestPoint, out vector3);
		        {
			        vector3.X = ((closestPoint.X >= 0f) ? box.Maximum.X : box.Minimum.X);
			        vector3.Y = ((closestPoint.Y >= 0f) ? box.Maximum.Y : box.Minimum.Y);
			        vector3.Z = ((closestPoint.Z >= 0f) ? box.Maximum.Z : box.Minimum.Z);
		        }

				Vector3 vector4;
				Vector3.Subtract(ref vector2, ref vector3, out vector4);
				float num2 = closestPoint.X * vector4.X + closestPoint.Y * vector4.Y + closestPoint.Z * vector4.Z;
				if (num2 > 0f)
				{
					break;
				}
				this.gjk.AddSupportPoint(ref vector4);
				closestPoint = this.gjk.ClosestPoint;
				float num3 = num;
				num = closestPoint.LengthSquared();
				if (num3 - num <= 1E-05f * num3)
				{
					return;
				}
				float num4 = 4E-05f * this.gjk.MaxLengthSquared;
				if (this.gjk.FullSimplex || num < num4)
				{
					goto IL_14A;
				}
			}
			return;
			IL_14A:
			result = true;
		}
		public bool Intersects(BoundingFrustum frustum)
		{
			if (frustum == null)
			{
				throw new ArgumentNullException("frustum");
			}
			if (this.gjk == null)
			{
				this.gjk = new Gjk();
			}
			this.gjk.Reset();
			Vector3 closestPoint;
			Vector3.Subtract(ref this.cornerArray[0], ref frustum.cornerArray[0], out closestPoint);
			if (closestPoint.LengthSquared() < 1E-05f)
			{
				Vector3.Subtract(ref this.cornerArray[0], ref frustum.cornerArray[1], out closestPoint);
			}
			float num = 3.40282347E+38f;
			while (true)
			{
				Vector3 vector;
				vector.X = -closestPoint.X;
				vector.Y = -closestPoint.Y;
				vector.Z = -closestPoint.Z;
				Vector3 vector2;
				this.SupportMapping(ref vector, out vector2);
				Vector3 vector3;
				frustum.SupportMapping(ref closestPoint, out vector3);
				Vector3 vector4;
				Vector3.Subtract(ref vector2, ref vector3, out vector4);
				float num2 = closestPoint.X * vector4.X + closestPoint.Y * vector4.Y + closestPoint.Z * vector4.Z;
				if (num2 > 0f)
				{
					break;
				}
				this.gjk.AddSupportPoint(ref vector4);
				closestPoint = this.gjk.ClosestPoint;
				float num3 = num;
				num = closestPoint.LengthSquared();
				float num4 = 4E-05f * this.gjk.MaxLengthSquared;
				if (num3 - num <= 1E-05f * num3)
				{
					return false;
				}
				if (this.gjk.FullSimplex || num < num4)
				{
					return true;
				}
			}
			return false;
		}
		public PlaneIntersectionType Intersects(Plane plane)
		{
			int num = 0;
			for (int i = 0; i < 8; i++)
			{
                float num2 = Vector3.Dot(this.cornerArray[i], plane.Normal);
				//Vector3.Dot(ref this.cornerArray[i], ref plane.Normal, out num2);

				if (num2 + plane.D > 0f)
				{
					num |= 1;
				}
				else
				{
					num |= 2;
				}
				if (num == 3)
				{
					return PlaneIntersectionType.Intersecting;
				}
			}
			if (num != 1)
			{
				return PlaneIntersectionType.Back;
			}
			return PlaneIntersectionType.Front;
		}
		public void Intersects(ref Plane plane, out PlaneIntersectionType result)
		{
			int num = 0;
			for (int i = 0; i < 8; i++)
			{
                float num2 = Vector3.Dot(this.cornerArray[i], plane.Normal);
				//Vector3.Dot(ref this.cornerArray[i], ref plane.Normal, out num2);
				if (num2 + plane.D > 0f)
				{
					num |= 1;
				}
				else
				{
					num |= 2;
				}
				if (num == 3)
				{
					result = PlaneIntersectionType.Intersecting;
					return;
				}
			}
			result = ((num == 1) ? PlaneIntersectionType.Front : PlaneIntersectionType.Back);
		}
		public float? Intersects(Ray ray)
		{
			float? result;
			this.Intersects(ref ray, out result);
			return result;
		}
		public void Intersects(ref Ray ray, out float? result)
		{
			ContainmentType containmentType;
			this.Contains(ref ray.Position, out containmentType);
			if (containmentType == ContainmentType.Contains)
			{
				result = new float?(0f);
				return;
			}
			float num = -3.40282347E+38f;
			float num2 = 3.40282347E+38f;
			result = null;
			Plane[] array = this.planes;
			for (int i = 0; i < array.Length; i++)
			{
				Plane plane = array[i];
				Vector3 normal = plane.Normal;
				float num3 = Vector3.Dot(ray.Direction, normal);
				//Vector3.Dot(ref ray.Direction, ref normal, out num3);
                float num4 = Vector3.Dot(ray.Position, normal);
				//Vector3.Dot(ref ray.Position, ref normal, out num4);
				num4 += plane.D;
				if (Math.Abs(num3) < 1E-05f)
				{
					if (num4 > 0f)
					{
						return;
					}
				}
				else
				{
					float num5 = -num4 / num3;
					if (num3 < 0f)
					{
						if (num5 > num2)
						{
							return;
						}
						if (num5 > num)
						{
							num = num5;
						}
					}
					else
					{
						if (num5 < num)
						{
							return;
						}
						if (num5 < num2)
						{
							num2 = num5;
						}
					}
				}
			}
			float num6 = (num >= 0f) ? num : num2;
			if (num6 >= 0f)
			{
				result = new float?(num6);
				return;
			}
		}
        
		public bool Intersects(BoundingSphere sphere)
		{
			bool result;
			this.Intersects(ref sphere, out result);
			return result;
		}
		public void Intersects(ref BoundingSphere sphere, out bool result)
		{
			if (this.gjk == null)
			{
				this.gjk = new Gjk();
			}
			this.gjk.Reset();
			Vector3 vector;
			Vector3.Subtract(ref this.cornerArray[0], ref sphere.Center, out vector);
			if (vector.LengthSquared() < 1E-05f)
			{
				vector = Vector3.UnitX;
			}
			float num = 3.40282347E+38f;
			result = false;
			while (true)
			{
				Vector3 vector2;
				vector2.X = -vector.X;
				vector2.Y = -vector.Y;
				vector2.Z = -vector.Z;
				Vector3 vector3;
				this.SupportMapping(ref vector2, out vector3);
				Vector3 vector4;
				//sphere.SupportMapping(ref vector, out vector4);
		        {
                    float _num2 = sphere.Radius / vector.Length();
                    vector4.X = sphere.Center.X + vector.X * _num2;
                    vector4.Y = sphere.Center.Y + vector.Y * _num2;
                    vector4.Z = sphere.Center.Z + vector.Z * _num2;
		        }

                Vector3 vector5;
				Vector3.Subtract(ref vector3, ref vector4, out vector5);
				float num2 = vector.X * vector5.X + vector.Y * vector5.Y + vector.Z * vector5.Z;
				if (num2 > 0f)
				{
					break;
				}
				this.gjk.AddSupportPoint(ref vector5);
				vector = this.gjk.ClosestPoint;
				float num3 = num;
				num = vector.LengthSquared();
				if (num3 - num <= 1E-05f * num3)
				{
					return;
				}
				float num4 = 4E-05f * this.gjk.MaxLengthSquared;
				if (this.gjk.FullSimplex || num < num4)
				{
					goto IL_138;
				}
			}
			return;
			IL_138:
			result = true;
		}
        
		public ContainmentType Contains(BoundingBox box)
		{
			bool flag = false;
			Plane[] array = this.planes;
			for (int i = 0; i < array.Length; i++)
			{
				Plane plane = array[i];
                PlaneIntersectionType planeIntersectionType = PlaneIntersectsBox(ref plane, ref box);
				if (planeIntersectionType == PlaneIntersectionType.Front)
				{
					return ContainmentType.Disjoint;
				}
				if (planeIntersectionType == PlaneIntersectionType.Intersecting)
				{
					flag = true;
				}
			}
			if (!flag)
			{
				return ContainmentType.Contains;
			}
			return ContainmentType.Intersects;
		}

        public static PlaneIntersectionType PlaneIntersectsBox(ref Plane plane, ref BoundingBox box)
        {
            Vector3 min;
            Vector3 max;

            max.X = (plane.Normal.X >= 0.0f) ? box.Minimum.X : box.Maximum.X;
            max.Y = (plane.Normal.Y >= 0.0f) ? box.Minimum.Y : box.Maximum.Y;
            max.Z = (plane.Normal.Z >= 0.0f) ? box.Minimum.Z : box.Maximum.Z;

            float distance;
            Vector3.Dot(ref plane.Normal, ref max, out distance);

            if (distance + plane.D > 0.0f)
                return PlaneIntersectionType.Front;

            min.X = (plane.Normal.X >= 0.0f) ? box.Maximum.X : box.Minimum.X;
            min.Y = (plane.Normal.Y >= 0.0f) ? box.Maximum.Y : box.Minimum.Y;
            min.Z = (plane.Normal.Z >= 0.0f) ? box.Maximum.Z : box.Minimum.Z;

            distance = Vector3.Dot(plane.Normal, min);

            if (distance + plane.D < 0.0f)
                return PlaneIntersectionType.Back;

            return PlaneIntersectionType.Intersecting;
        }

		public void Contains(ref BoundingBox box, out ContainmentType result)
		{
			bool flag = false;
			Plane[] array = this.planes;
			for (int i = 0; i < array.Length; i++)
			{
				Plane plane = array[i];
                PlaneIntersectionType planeIntersectionType = box.Intersects(ref plane);
				if (planeIntersectionType == PlaneIntersectionType.Front)
				{
					result = ContainmentType.Disjoint;
					return;
				}
				if (planeIntersectionType == PlaneIntersectionType.Intersecting)
				{
					flag = true;
				}
			}
			result = (flag ? ContainmentType.Intersects : ContainmentType.Contains);
		}
		public ContainmentType Contains(BoundingFrustum frustum)
		{
			if (frustum == null)
			{
				throw new ArgumentNullException("frustum");
			}
			ContainmentType result = ContainmentType.Disjoint;
			if (this.Intersects(frustum))
			{
				result = ContainmentType.Contains;
				for (int i = 0; i < this.cornerArray.Length; i++)
				{
					if (this.Contains(frustum.cornerArray[i]) == ContainmentType.Disjoint)
					{
						result = ContainmentType.Intersects;
						break;
					}
				}
			}
			return result;
		}
		public ContainmentType Contains(Vector3 point)
		{
			Plane[] array = this.planes;
			for (int i = 0; i < array.Length; i++)
			{
				Plane plane = array[i];
				float num = plane.Normal.X * point.X + plane.Normal.Y * point.Y + plane.Normal.Z * point.Z + plane.D;
				if (num > 1E-05f)
				{
					return ContainmentType.Disjoint;
				}
			}
			return ContainmentType.Contains;
		}
		public void Contains(ref Vector3 point, out ContainmentType result)
		{
			Plane[] array = this.planes;
			for (int i = 0; i < array.Length; i++)
			{
				Plane plane = array[i];
				float num = plane.Normal.X * point.X + plane.Normal.Y * point.Y + plane.Normal.Z * point.Z + plane.D;
				if (num > 1E-05f)
				{
					result = ContainmentType.Disjoint;
					return;
				}
			}
			result = ContainmentType.Contains;
		}
		public ContainmentType Contains(BoundingSphere sphere)
		{
			Vector3 center = sphere.Center;
			float radius = sphere.Radius;
			int num = 0;
			Plane[] array = this.planes;
			for (int i = 0; i < array.Length; i++)
			{
				Plane plane = array[i];
				float num2 = plane.Normal.X * center.X + plane.Normal.Y * center.Y + plane.Normal.Z * center.Z;
				float num3 = num2 + plane.D;
				if (num3 > radius)
				{
					return ContainmentType.Disjoint;
				}
				if (num3 < -radius)
				{
					num++;
				}
			}
			if (num != 6)
			{
				return ContainmentType.Intersects;
			}
			return ContainmentType.Contains;
		}
		public void Contains(ref BoundingSphere sphere, out ContainmentType result)
		{
			Vector3 center = sphere.Center;
			float radius = sphere.Radius;
			int num = 0;
			Plane[] array = this.planes;
			for (int i = 0; i < array.Length; i++)
			{
				Plane plane = array[i];
				float num2 = plane.Normal.X * center.X + plane.Normal.Y * center.Y + plane.Normal.Z * center.Z;
				float num3 = num2 + plane.D;
				if (num3 > radius)
				{
					result = ContainmentType.Disjoint;
					return;
				}
				if (num3 < -radius)
				{
					num++;
				}
			}
			result = ((num == 6) ? ContainmentType.Contains : ContainmentType.Intersects);
		}
		internal void SupportMapping(ref Vector3 v, out Vector3 result)
		{
			int num = 0;
            float num2 = Vector3.Dot(this.cornerArray[0], v);
			//Vector3.Dot(ref this.cornerArray[0], ref v, out num2);
			for (int i = 1; i < this.cornerArray.Length; i++)
			{
                float num3 = Vector3.Dot(this.cornerArray[i], v);
				//Vector3.Dot(ref this.cornerArray[i], ref v, out num3);
				if (num3 > num2)
				{
					num = i;
					num2 = num3;
				}
			}
			result = this.cornerArray[num];
		}
		public static bool operator ==(BoundingFrustum a, BoundingFrustum b)
		{
			return object.Equals(a, b);
		}
		public static bool operator !=(BoundingFrustum a, BoundingFrustum b)
		{
			return !object.Equals(a, b);
		}
	}
}
